﻿using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.Data;

using System.Windows.Forms;
using System.Drawing;
using System.IO;

namespace DELVE_GAME
{

    public class Board
    {
        bool futureBool = false;
        string name;
        int WIDTH;
        int HEIGHT;
        DataTable myBoard;
        List<Group> startingAreas;
        List<Group> territories; //groups should be initilized from the gamesettings
        Group raceTerritory;
        int occupiableTiles;

        #region boardCopy
        /// <summary>
        /// deep copy constructor for board
        /// </summary>
        /// <param name="currentBoard"></param>
        public Board(Board currentBoard)
        {
            futureBool = true;
            this.name = currentBoard.name;
            this.WIDTH = currentBoard.WIDTH;
            this.HEIGHT = currentBoard.HEIGHT;
            this.myBoard = currentBoard.myBoard.Clone();


            //this.myBoard = currentBoard.myBoard.Copy();
            /*foreach(DataColumn dc in currentBoard.myBoard.Columns){
                this.myBoard.Columns.Add(dc);
            }*/
            for (int j = 0; j < this.HEIGHT; j++)
            {
                DataRow dr = this.myBoard.NewRow();
                for (int i = 0; i < this.WIDTH; i++)
                {
                    dr[i.ToString()] = new Tile((Tile)currentBoard.myBoard.Rows[j][i.ToString()]);
                }
                this.myBoard.Rows.Add(dr);
            }

            this.configureAdjacents();
            /*for (int j = 0; j < this.HEIGHT; j++)
            {
                //DataRow dr = this.myBoard.NewRow();
                for (int i = 0; i < this.WIDTH; i++)
                {
                    /*
                    List<Tile> newAdj = new List<Tile>();
                    foreach(Tile t in ((Tile)this.myBoard.Rows[j][i.ToString()]).getAdjacents()){
                        t
                    } = new Tile((Tile)currentBoard.myBoard.Rows[j][i.ToString()]);
                }
                //this.myBoard.Rows.Add(dr);
            }*/
            /*foreach (DataRow dr in currentBoard.myBoard.Rows) {
                DataRow newDr = this.myBoard.NewRow();
                
                this.myBoard.Rows.Add(newDr);
            }*/

            this.startingAreas = currentBoard.startingAreas;
            this.territories = currentBoard.territories;
            this.raceTerritory = currentBoard.raceTerritory;
            this.occupiableTiles = currentBoard.occupiableTiles;
        }
        #endregion


        #region board initialization

        public Board(Map map)
        {
            myBoard = new DataTable();
            List<Tile> wifiTiles = new List<Tile>();

            name = map.name;
            Tile[][] tiles = map.board;
            WIDTH = tiles.Length;
            HEIGHT = tiles[0].Length;

            for (int i = 0; i < WIDTH; i++)
            {
                myBoard.Columns.Add(new DataColumn(i.ToString(), typeof(Tile)));
            }
            for (int i = HEIGHT - 1; i >= 0; i--)
            {
                DataRow row = myBoard.NewRow();
                for (int j = 0; j < WIDTH; j++)
                {
                    Tile tile = null;
                    Point p = new Point(j, i);
                    /*A mutual change*/switch (tiles[j][i].type)
                    {
                        case (int)TILE_VARIANTS.TILE:
                            tile = new Tile(p);
                            occupiableTiles++;
                            break;
                        case (int)TILE_VARIANTS.HIGH_BANDWIDTH:
                            tile = new HighBandwidthTile(p);
                            occupiableTiles++;
                            break;
                        case (int)TILE_VARIANTS.LOW_BANDWIDTH:
                            tile = new LowBandwidthTile(p);
                            occupiableTiles++;
                            break;

                        case (int)TILE_VARIANTS.WIFI:
                            tile = new WiFiTile(p);
                            occupiableTiles++; break;
                        case (int)TILE_VARIANTS.BROKEN_LINK:
                            tile = new BrokenLinkTile(p);
                            occupiableTiles++; break;
                        case (int)TILE_VARIANTS.BUS:
                            tile = new BusTile(p, tiles[i][j].orientation);
                            occupiableTiles++; break;
                        case (int)TILE_VARIANTS.FIREWALL:
                            tile = new FirewallTile(p, tiles[i][j].orientation);
                            occupiableTiles++; break;
                        case (int)TILE_VARIANTS.DYING_LINK:
                            tile = new DyingLinkTile(p);
                            occupiableTiles++; break;
                    }
                    row[j.ToString()] = tile;
                }
                myBoard.Rows.Add(row);
            }

            startingAreas = new List<Group>();
            foreach (List<Point> list in map.startingAreas)
            {
                startingAreas.Add(convertPointToGroup(list));
            }

            territories = new List<Group>();
            foreach (List<Point> list in map.territories)
            {
                territories.Add(convertPointToGroup(list));
            }

            raceTerritory = convertPointToGroup(map.raceTerritory);

            configureAdjacents();

        }
        
        public Board()
        {
            myBoard = new DataTable();
            List<Tile> wifiTiles = new List<Tile>();

            using (var reader = File.OpenText("Content/Maps/testMap.txt"))
            {
                string line = reader.ReadLine();
                string[] mapStats = line.Split(' ');
                WIDTH = Convert.ToInt32(mapStats[1]);
                HEIGHT = Convert.ToInt32(mapStats[3]);
                for (int i = 0; i < WIDTH; i++)
                {
                    myBoard.Columns.Add(new DataColumn(i.ToString(), typeof(Tile)));
                }
                for (int i = HEIGHT - 1; i >= 0; i--)
                {
                    line = reader.ReadLine();
                    DataRow row = myBoard.NewRow();
                    for (int j = 0; j < WIDTH; j++)
                    {
                        Tile tile = null;
                        Point p = new Point(j, i);
                        switch (line[j])
                        {
                            case 'l': tile = new LowBandwidthTile(p);
                                occupiableTiles++; break;
                            case 'h': tile = new HighBandwidthTile(p);
                                occupiableTiles++; break;
                            case 'w': tile = new WiFiTile(p);
                                wifiTiles.Add(tile);
                                occupiableTiles++; break;
                            case 'b': tile = new BrokenLinkTile(p); break;

                            case 'f': tile = new FirewallTile(p, (int)DIRECTIONS.LEFT); break;
                            case 'u': tile = new BusTile(p, (int)DIRECTIONS.UP); break;
                            case 'd': tile = new DyingLinkTile(p); break;
                            default: tile = new Tile(p);
                                occupiableTiles++; break;
                        }
                        row[j.ToString()] = tile;
                    }
                    myBoard.Rows.Add(row);
                }
        
                startingAreas = new List<Group>();
                territories = new List<Group>();

                line = reader.ReadLine();
                if (line != null && line.Equals("Starting Areas"))
                {
                    line = reader.ReadLine();
                    while (line != null && !line.Equals("Territories"))
                    {
                        //assumes corrent number of starting areas!
                        startingAreas.Add(readGroup(line.Split(' ')));
                        line = reader.ReadLine();
                    }
                }
                if (line != null && line.Equals("Territories"))
                {
                    line = reader.ReadLine();
                    while (line != null && !line.Equals("Race Territory"))
                    {
                        territories.Add(readGroup(line.Split(' ')));
                        line = reader.ReadLine();
                    }
                }
                if (line != null && line.Equals("Race Territory"))
                {
                    line = reader.ReadLine();
                    if (line != null)
                        raceTerritory = readGroup(line.Split(' '));
                }

                reader.Close();

                configureAdjacents();
            }
        }
        

        private Group readGroup(string[] stats)
        {
            int startX = Convert.ToInt32(stats[0]);
            int startY = Convert.ToInt32(stats[1]);
            int endX = Convert.ToInt32(stats[3]);
            int endY = Convert.ToInt32(stats[4]);
            HashSet<Tile> groupTiles = new HashSet<Tile>();

            for (int i = startX; i <= endX; ++i)
            {
                for (int j = startY; j <= endY; ++j)
                {
                    Point newPoint = new Point(i, j);
                    if (!(getLocation(newPoint) is BrokenLinkTile))
                    {
                        groupTiles.Add(getLocation(newPoint));
                    }
                }
            }
            return new Group(groupTiles);
        }

        private Group convertPointToGroup(List<Point> points)
        {
            HashSet<Tile> tiles = new HashSet<Tile>();
            foreach (Point p in points)
            {
                tiles.Add(getLocation(p));
            }
            Group newGroup = new Group(tiles);
            return newGroup;
        }

        public void configureAdjacents()
        {
            HashSet<Tile> brokenLinkTiles = new HashSet<Tile>();
            HashSet<Tile> wifiTiles = new HashSet<Tile>();
            HashSet<Tile> busTiles = new HashSet<Tile>();
            HashSet<Tile> firewallTiles = new HashSet<Tile>();

            //now initilize all tiles to know their adjacents
            for (int i = 0; i < HEIGHT; i++)
            {
                DataRow row = myBoard.Rows[i];
                for (int j = 0; j < WIDTH; j++)
                {
                    Tile myTile = (Tile)row[j.ToString()];
                    Tile adjacent;
                    List<Tile> adjs = new List<Tile>();
                    if (j > 0)
                    {//add leftAdjacent
                        adjacent = (Tile)row[(j - 1).ToString()];
                        adjs.Add(adjacent);
                    }
                    if (j < WIDTH - 1)
                    {//add rightAdjacent
                        adjacent = (Tile)row[(j + 1).ToString()];
                        adjs.Add(adjacent);
                    }
                    if (i < HEIGHT - 1)
                    {//add topAdjacent
                        adjacent = (Tile)myBoard.Rows[i + 1][j.ToString()];
                        adjs.Add(adjacent);
                    }
                    if (i > 0)
                    {//add bottomAdjacent
                        adjacent = (Tile)myBoard.Rows[i - 1][j.ToString()];
                        adjs.Add(adjacent);
                    }
                    switch (myTile.type)
                    {
                        case (int)TILE_VARIANTS.WIFI: wifiTiles.Add(myTile); break;
                        case (int)TILE_VARIANTS.BROKEN_LINK: brokenLinkTiles.Add(myTile); break;
                        case (int)TILE_VARIANTS.BUS: busTiles.Add(myTile); break;
                        case (int)TILE_VARIANTS.FIREWALL: firewallTiles.Add(myTile); break;
                    }
                    myTile.placeAdjacents(adjs);
                }
            }

            foreach (Tile t in wifiTiles)
            {
                List<Tile> tempAdjs = t.getAdjacents();
                foreach (Tile wifi in wifiTiles)
                {
                    if (wifi != t) tempAdjs.Add(wifi);
                }
            }
            foreach (Tile t in brokenLinkTiles)
            {
                Point tilePos = t.getLocation();
                int tilePosX = tilePos.X;
                int tilePosY = tilePos.Y;
                if (tilePos.X > 0)
                    getLocation(new Point(tilePosX - 1, tilePosY)).getAdjacents().Remove(t);
                if (tilePos.X < WIDTH - 1)
                    getLocation(new Point(tilePosX + 1, tilePosY)).getAdjacents().Remove(t);
                if (tilePos.Y < HEIGHT - 1)
                    getLocation(new Point(tilePosX, tilePosY + 1)).getAdjacents().Remove(t);
                if (tilePos.Y > 0)
                    getLocation(new Point(tilePosX, tilePosY - 1)).getAdjacents().Remove(t);

            }
            foreach (Tile t in busTiles)
            {
                int modX1 = t.getLocation().X;
                int modY1 = t.getLocation().Y;
                int modX2 = modX1;
                int modY2 = modY1;
                int orientation = t.orientation;
                t.setOrientation(orientation);
                switch (orientation)
                {
                    case (int)DIRECTIONS.LEFT:
                    case (int)DIRECTIONS.RIGHT: modY1++; modY2--; break;
                    case (int)DIRECTIONS.UP:
                    case (int)DIRECTIONS.DOWN: modX1++; modX2--; break;
                }
                if (modY2 >= 0 && modY1 < HEIGHT && modX2 >= 0 && modX1 < WIDTH)
                {
                    getLocation(new Point(modX1, modY1)).getAdjacents().Remove(t);
                    getLocation(new Point(modX2, modY2)).getAdjacents().Remove(t);
                }
            }
            foreach (Tile t in firewallTiles)
            {
                t.setOrientation(t.orientation);
            }


        }

        #endregion

        public void printBoard()
        {
            for (int j = 0; j < this.HEIGHT; j++)
            {
                Console.Out.WriteLine();
                for (int i = 0; i < this.WIDTH; i++)
                {
                    Console.Out.Write(((Tile)this.myBoard.Rows[j][i.ToString()]).ToString() + " ");
                }
            }
        }

        #region tests for groups

        public int occupyingTerritories(int playerIndex)
        {
            int occupying = 0;
            foreach (Group g in territories)
            {
                if (g.isOccupiedBy(playerIndex)) occupying++;
            }
            return occupying;
        }

        public bool occupyingRaceTerritory(int playerIndex)
        {
            return raceTerritory.isOccupiedBy(playerIndex);
        }

        public bool tileInTerritory(Tile tile, int gameType)
        {
            bool isInTerritory = false;

            switch (gameType)
            {
                case (int)GameType.TERRITORIES:
                    foreach (Group territory in territories)
                    {
                        if (territory.isInGroup(tile))
                        {
                            isInTerritory = true;
                            break;
                        }
                    }
                    break;
                case (int)GameType.TERRITORY_RACE:
                    if (raceTerritory.isInGroup(tile))
                        isInTerritory = true;
                    break;
            }
            return isInTerritory;
        }

        public bool tileInStartingArea(Tile tile, int playerIndex)
        {
            bool isInStartingArea = false;
            if (startingAreas[playerIndex].isInGroup(tile))
            {
                isInStartingArea = true;
            }
            return isInStartingArea;
        }

        #endregion

        #region getters and setters

        public DataTable getBoard() { return myBoard; }
        public int getOccupiableTiles() { return occupiableTiles; }
        public Tile getLocation(Point point)
        {
            return (Tile)(myBoard.Rows[HEIGHT - point.Y - 1])[(point.X).ToString()];
            //point needs to be scaled to the data grid view of 0-24
        }
        public void setLocation(Point point, Virus v)
        {
            ((Tile)myBoard.Rows[HEIGHT - point.Y - 1][(point.X).ToString()]).plantVirus(v);
        }
        public int getWidth() { return WIDTH; }
        public int getHeight() { return HEIGHT; }
        public bool hasTerritories() { return territories.Count > 0; }
        public bool hasRaceTerritory() { return raceTerritory.getNumTiles() > 0; }
        public int getMaxNumPlayers() { return startingAreas.Count; }

        #endregion
    }
}
